package com.mornd.gulimall.product.utils;

import cn.hutool.core.lang.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * @author: mornd
 * @dateTime: 2023/8/12 - 1:40
 * redis 实现的简易分布式锁
 * 无法保证刚好在业务执行完时释放锁，有可能会提前释放锁
 */
@Slf4j
public class RedisLock {

    /**
     * 锁名称
     */
    private final String KEY;
    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 在集群环境下，每个线程id也是唯一的，UUID + 线程id
     */
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

    private static final RedisScript<Long> UNLOCK_SCRIPT;

    static {
        // 加载 lua 脚本
        UNLOCK_SCRIPT = RedisScript.of(new ClassPathResource("script/unlock.lua"), Long.class);
    }

    public RedisLock(String key, StringRedisTemplate stringRedisTemplate) {
        this.KEY = key;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 获取锁
     *
     * @param timeSec 多少秒过后自动释放锁
     * @return 是否成功获取到锁
     */
    public boolean tryLock(long timeSec) {
        // uuid + 线程id
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        log.debug("尝试获取锁，threadId：{}", threadId);

        Boolean absent = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY, threadId, timeSec, TimeUnit.SECONDS);

        log.debug("尝试获取锁结束，结果={}，threadId={}", absent, threadId);
        return Boolean.TRUE.equals(absent);
    }

    /**
     * lua 脚本解锁
     */
    public void unlock() {
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        Long result = stringRedisTemplate
                .execute(UNLOCK_SCRIPT, Collections.singletonList(KEY), threadId);
    }

    /**
     * 解锁，获取锁和删除锁不具备原子性
     */
    public void unlock2() {
        String value = stringRedisTemplate.opsForValue().get(KEY);

        String threadId = ID_PREFIX + Thread.currentThread().getId();
        // 保证上锁和解锁是同一个线程
        if (threadId.equals(value)) {
            stringRedisTemplate.delete(KEY);
        }
    }

}
